//
// Created by jmdvirus on 2020/3/12 下午2:42.
// Copyright (c) 2020 benewtech All rights reserved.
//
//     ******          *             *--*
//       ||            *             |
//       ||  *--*--*---*  * *-********--*
//   *  //   *  *  *   *\ / ||  |  |    |
//    **    *  *  *---* *  *   *--* *--*
//

#include "CMMediaVideo.h"
#include "cm_logprint.h"

#include <sys/prctl.h>
#include <unistd.h>

CMMediaVideo::CMMediaVideo()
{
    m_pid = 0;
    m_videoWidth = 640;
    m_videoHeight = 360;
    m_sizeChanged = false;
    m_media = NULL;
    pthread_mutexattr_init(&m_lockAttrT);
    pthread_mutexattr_settype(&m_lockAttrT, PTHREAD_MUTEX_ERRORCHECK_NP);
    m_needConvert = true;
    m_skipFirstFrames = false;
}

CMMediaVideo::CMMediaVideo(int index)
{
    m_pid = 0;
    pthread_mutexattr_init(&m_lockAttrT);
    pthread_mutexattr_settype(&m_lockAttrT, PTHREAD_MUTEX_ERRORCHECK_NP);
    m_needConvert = true;
    m_skipFirstFrames = false;
    open(index);
}

CMMediaVideo::~CMMediaVideo()
{
    close();
    if (m_media)
    {
        delete m_media;
    }
    m_media = NULL;
    pthread_mutexattr_destroy(&m_lockAttrT);
}

int CMMediaVideo::registerFunc(CMMediaVideoFunction *func)
{
    m_lock.lock(__func__, __LINE__);
    bool exist = false;
    for (auto &i : m_funcs)
    {
        if (i == func)
        {
            exist = true;
            break;
        }
    }
    if (!exist)
    {
        m_funcs.push_back(func);
    }
    m_lock.unlock();
    cminfo("funcs after add remain = [%d]\n", m_funcs.size());
    return m_funcs.size();
}

int CMMediaVideo::unregisterFunc(CMMediaVideoFunction *func)
{
    m_lock.lock(__func__, __LINE__);
    std::vector<CMMediaVideoFunction *>::const_iterator iter = m_funcs.begin();
    while (iter != m_funcs.end())
    {
        if (*iter == func)
        {
            iter = m_funcs.erase(iter);
        }
        else
        {
            iter++;
        }
    }
    m_lock.unlock();
    cminfo("funcs after remove remain = [%d]\n", m_funcs.size());
    return m_funcs.size();
}

void *thread_media_video(void *args)
{
    prctl(PR_SET_NAME, "MediaVideo", 0, 0, 0);
    CMMediaVideo *m_mediaVideo = (CMMediaVideo *)args;
    cminfo("thread media video start handle = [%p]\n", m_mediaVideo);
    m_mediaVideo->m_lockT.lock(__func__ , __LINE__);
    cminfo("start to get frame\n");
    int ret = 0;
    while (!m_mediaVideo->m_stop)
    {
        ret = m_mediaVideo->thread_once();
        if (ret < 0)
        {
            usleep(40000);
        }
    }
    //    sem_post(&m_mediaVideo->m_sem);

    m_mediaVideo->m_pid = 0;
    cminfo("thread media video stop done\n");
    m_mediaVideo->m_lockT.unlock();

    return NULL;
}

int CMMediaVideo::thread_once()
{
    m_lockFrame.lock(__func__, __LINE__);
#ifdef MEDIA_USE_OPENCV
    bool ret = m_media->readMat(m_currentFrame) >= 0;
#else
    unsigned char *buf = NULL;
    int c = 0;
    // skip some frame when first start,
    // skip some bad frame maybe
    while (m_skipFirstFrames)
    {
        if (c++ > 5)
        {
            m_skipFirstFrames = false;
        }
        m_media->readYUV(&buf);
    }
//    int ret = m_media->readYUV(&buf) >= 0;
        size_t len = 0;
        bool ret = m_media->readYUV(&buf, &len) >= 0;
    if (ret > 0 && buf)
    {
        cv::Mat cFrame(m_videoHeight * 3 / 2, m_videoWidth, CV_8UC1, buf);
        m_currentFrame = cFrame;
        //        m_media->readClear();
    }
#endif
    m_lockFrame.unlock();

    if (ret > 0)
    {
        if (m_needConvert)
        {
            cv::cvtColor(m_currentFrame, m_convertFrame, cv::COLOR_YUV2BGR_I420);
        }
        m_lock.lock(__func__, __LINE__);
        for (auto &i : m_funcs)
        {
            i->onFrame(m_currentFrame, m_convertFrame);
        }
        m_lock.unlock();
    }

    return ret > 0 ? 0 : -1;
}

void CMMediaVideo::setSize(int width, int height)
{
    if (width != m_videoWidth || height != m_videoHeight)
    {
        m_videoWidth = width;
        m_videoHeight = height;
        m_sizeChanged = true;
    }
}

int CMMediaVideo::open(int index)
{
    m_currentFrame = cv::Mat();
    m_skipFirstFrames = false;
    if (!m_media)
    {
#ifdef MEDIA_USE_OPENCV
        m_media = new CMMediaOperationOpenCV();
#else
#ifdef WITH_MEDIAVIDEO_RAW
        m_media = new CMMediaOperationRaw();
#else
        return -1;
#endif
#endif
    }
    m_stop = false;
    if (m_media->isOpened())
    {
        return 0;
    }
    cminfo("set video size [%d,%d], index = [%d]\n", m_videoWidth, m_videoHeight, index);
    m_media->setParam(m_videoWidth, m_videoHeight);
    int r = m_media->open(index);
    if (r >= 0)
    {
        r = pthread_create(&m_pid, NULL, thread_media_video,
                                                 this);
        if (r != 0)
        {
            cmerror("Thread create fail\n");
            return -1;
        }

        cminfo("open video index = [%d], success pid = [%d]\n", index, m_pid);
        return 0;
    }
    m_media->close();
    cmerror("open video index = [%d] fail ret = [%d]\n", index , r);
    return -2;
}

int CMMediaVideo::close()
{
    if (!m_media->isOpened())
        return 0;
    m_lock.lock(__func__, __LINE__);
    if (m_funcs.size() > 0)
    {
        for (auto &i : m_funcs)
        {
            cminfo("func [%s] not unregsiter, will never close video\n", i
            ->getName().c_str());
        }
        m_lock.unlock();
        return -1;
    }
    m_lock.unlock();
    m_stop = true;
    cminfo("Thread to exit pid = [%d]\n", m_pid);
    m_lockT.lock(__func__ ,__LINE__);
    m_media->close();
    m_pid = 0;
    cminfo("Thread exit\n");
    m_needConvert = true;
    m_lockT.unlock();
    return 0;
}

int CMMediaVideo::getFrame(cv::Mat &frame)
{
    m_lockFrame.lock(__func__, __LINE__);
    frame = m_currentFrame;
    m_lockFrame.unlock();
    return 0;
}
